/*
 * Copyright (c) 2020-2099 the original author or authors. All rights reserve.
 */
package org.tianyun.cloud.datasource.autoconfigure;

import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.metadata.DataSourcePoolMetadataProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.tianyun.cloud.config.console.annotaion.ConsoleDiscovery;
import org.tianyun.cloud.config.refresh.smart.annotation.SmartScope;
import org.tianyun.cloud.datasource.DataSourceFactoryConfigurer;
import org.tianyun.cloud.datasource.DataSourceFactoryPostProcessor;
import org.tianyun.cloud.datasource.DataSourceFactoryRegistrar;
import org.tianyun.cloud.datasource.WrappedDataSource;
import org.tianyun.cloud.datasource.annotation.DataSourceContainer;
import org.tianyun.cloud.datasource.configuration.DataSourceFactoryProperties;
import org.tianyun.cloud.datasource.configuration.DataSourceProperties;
import org.tianyun.cloud.datasource.configuration.ShardingDataSourceProperties;
import org.tianyun.cloud.datasource.enums.ClientType;
import org.tianyun.cloud.datasource.routing.DataSourceFactory;
import org.tianyun.cloud.datasource.routing.RoutingDataSource;
import org.tianyun.cloud.datasource.routing.RoutingWithAspect;
import org.tianyun.cloud.datasource.routing.transaction.RoutingDataSourceTransactionManager;
import org.tianyun.cloud.datasource.sharding.NotAvailableDataSourcePoolMetadata;
import org.tianyun.cloud.datasource.sharding.ShardingDataSourceFactory;

import javax.sql.DataSource;
import java.sql.SQLException;
import java.util.List;

/**
 * 数据源自动装配
 *
 * @auther ebert_chan
 */
@Configuration(proxyBeanMethods = false)
@Import(DataSourceFactoryRegistrar.class)
@EnableTransactionManagement
public class DataSourceAutoConfiguration {

    public static final String COMMON_DATASOURCE_NAME = "commonDataSource";

    @Bean
    @ConfigurationProperties(prefix = "datasource", ignoreInvalidFields = true)
    public DataSourceProperties dataSourceProperties() {
        return new DataSourceProperties();
    }

    /**
     * 自定义routing数据源初始化
     */
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnProperty(prefix = "datasource", name = "client-type", havingValue = "routing", matchIfMissing = true)
    public static class RoutingDataSourceConfiguration {

        @DataSourceContainer(prefix = "datasource.routing.common", datasource = COMMON_DATASOURCE_NAME)
        public DataSourceFactoryProperties dataSourceFactoryProperties() {
            return new DataSourceFactoryProperties();
        }

        @Bean
        public DataSourceFactoryPostProcessor dataSourceFactoryPostProcessor(ObjectProvider<List<DataSourceFactoryConfigurer>> dataSourceFactoryConfigurerProvider) {
            return new DataSourceFactoryPostProcessor(dataSourceFactoryConfigurerProvider);
        }

        @Bean
        public RoutingWithAspect routingWithAspect() {
            return new RoutingWithAspect();
        }

        @Bean
        public DataSourceTransactionManager transactionManager(DataSource dataSource) {
            RoutingDataSourceTransactionManager dtm = new RoutingDataSourceTransactionManager();
            dtm.setDataSource(dataSource);
            return dtm;
        }

    }

    /**
     * sharding-jdbc数据源初始化
     */
    @Configuration(proxyBeanMethods = false)
    @ConditionalOnProperty(prefix = "datasource", name = "client-type", havingValue = "sharding")
    public static class ShardingDataSourceConfiguration {

        @Bean
        @ConfigurationProperties(prefix = "datasource.sharding", ignoreInvalidFields = true)
        @SmartScope(prefix = "datasource.sharding")
        public ShardingDataSourceProperties shardingDataSourceProperties() {
            return new ShardingDataSourceProperties();
        }

        @Bean
        @SmartScope(prefix = "datasource.sharding")
        @ConsoleDiscovery(namespace = ConsoleDiscovery.NAMESPACE_DATASOURCE_SHARDING, prefix = "datasource.sharding", beanName = "shardingDataSource")
        public ShardingDataSourceFactory shardingDataSource(ShardingDataSourceProperties shardingDataSourceProperties) throws SQLException {
            return new ShardingDataSourceFactory(shardingDataSourceProperties);
        }

        /**
         * 解决新版Spring中,集成sharding jdbc 健康检查异常
         */
        @Bean
        public DataSourcePoolMetadataProvider dataSourcePoolMetadataProvider() {
            return dataSource -> new NotAvailableDataSourcePoolMetadata();
        }

    }

    @Bean
    public DataSource dataSource(DataSourceProperties dataSourceProperties, ObjectProvider<ShardingDataSourceFactory> shardingDataSourceFactory, ObjectProvider<List<DataSourceFactory>> clusters) throws SQLException {
        DataSource dataSource;
        if (ClientType.sharding.equals(dataSourceProperties.getClientType())) {
            dataSource = new WrappedDataSource(shardingDataSourceFactory.getIfAvailable());
        } else {
            dataSource = new RoutingDataSource(clusters.getIfAvailable());
        }
        return dataSource;
    }

}
